import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.Random;

/**
 * 
 * @author Rony Lahav 043489889
 *
 */

public class GermsMatrix {

	private static LinkedList<Germ> germs = null;	//will contain the germs population
	
	public static byte[][] foodMatrix=null;			//will contain the food locations in the matrix
	public static byte[][] germsMatrix=null;		//will contain the germs locations in the matrix
	public static int N=0;							//will contain matrix size
	
	public static int[] locMUp=null;				//will contain the upper-left corner of the fluent food region
	public static int[] locMDown=null;				//will contain the bottom-right corner of the fluent food region
	private static int M=0;							//will contain fluent food region size
	
	public static float maxEnergy=0;				//will contain the maximum amount of energy allowed for the germs
	private static float E=0;						//will contain initial energy for germs
	
	public static int splitAge=0;					//will contain the "age" for a possible split
	public static float minSplitE=0;				//will contain the minimum energy amount required for a split
	
	private static int steps=0;						//will contain the numbers of steps to run
	private static int popSize=0;					//will contain the population size
	private static int maxPopSize=0;				//will contain the maximum allowed population size
	
	private static float[][] graphData=null;		//will contain energy levels in each step for the graph
	
	/* Reads the germs data and updates them into the graph data according to their location,
	 * Where if they are in the fluent food area it's updated to region "1", else to region "0".
	 * I read the energy levels as well as the adventure levels to help the analysis, and also added population size
	 */
	private static void updateGraphData(){
		float[] data=null;
		Germ germ = null;
		int countInFluent=0;
		if (popSize>0){
			for(int i=0;i<popSize;++i){
				germ=germs.get(i);
				data=germ.reportData();
				int region=(int) data[0];
				if (region==1)
					++countInFluent;
				graphData[region][steps-1] += data[1];
				graphData[region+2][steps-1] += data[2];
			}
			//Average energy level in both regions
			graphData[0][steps-1]/=(popSize-countInFluent);
			graphData[1][steps-1]/=countInFluent;
			//Average adventure level in both regions
			graphData[2][steps-1]/=(popSize-countInFluent);
			graphData[3][steps-1]/=countInFluent;
			//Population sizes in both areas
			graphData[4][steps-1]=(popSize-countInFluent);
			graphData[5][steps-1]=countInFluent;
		}
		else{
			graphData[0][steps-1]=0;
			graphData[1][steps-1]=0;
			graphData[2][steps-1]=0;
			graphData[3][steps-1]=0;
			graphData[4][steps-1]=0;
			graphData[5][steps-1]=0;
		}
	}
	
	/* After the run has finished the graph data is printed to an excel csv file,
	 * So we'll be able to generate the results graph easily
	 */
	private static void printGraphData() throws IOException {
		PrintWriter out = new PrintWriter("graphData.csv");
		//print header of serial steps numbers
		String header="step,";
		for (int i=0;i<graphData[0].length-1;++i)
			header+=Integer.toString(i+1) + ",";
		out.println(header);
		//print the energy levels in both areas (printed in reverse because it was stored in reverse)
		for(int i=0;i<2;++i){
			String strEnergy = "";
			if (i==0)
				strEnergy+="regular-energy,";
			else
				strEnergy+="fluent-energy,";
			for (int j=graphData[0].length-1;j>=0;--j)
				strEnergy+=(Float.toString(graphData[i][j]) + ",");
			strEnergy=strEnergy.substring(0, strEnergy.length()-1);
			out.println(strEnergy);
		}
		//print the adventure levels in both areas (printed in reverse because it was stored in reverse)
		for(int i=0;i<2;++i){
			String strAdventure = "";
			if (i==0)
				strAdventure+="regular-adventure,";
			else
				strAdventure+="fluent-adventure,";
			for (int j=graphData[2].length-1;j>=0;--j)
				strAdventure+=(Float.toString(graphData[i+2][j]) + ",");
			strAdventure=strAdventure.substring(0, strAdventure.length()-1);
			out.println(strAdventure);
		}
		//print the population sizes in both areas (printed in reverse because it was stored in reverse)
		for(int i=0;i<2;++i){
			String strPopulation = "";
			if (i==0)
				strPopulation+="regular-population,";
			else
				strPopulation+="fluent-population,";
			for (int j=graphData[4].length-1;j>=0;--j)
				strPopulation+=(Float.toString(graphData[i+4][j]) + ",");
			strPopulation=strPopulation.substring(0, strPopulation.length()-1);
			out.println(strPopulation);
		}
		out.close();
	}
	
	/* The matrix is randomly filled with food.
	 * Each germ in the population performs his step and updates his values (and the matrix),
	 * and then each germ reports his location and energy values for the graph.
	 */
	private static void performStep(){
		LinkedList<Germ> newGerms = new LinkedList<Germ>();	//for all new germs from splits (if any)
		LinkedList<Germ> deadGerms = new LinkedList<Germ>();//for all germs that died this step (if any)
		fillFood();
		//perform a step for all the germs, and update them as dead or split them as necessary
		for (Germ germ : germs) {
			germ.performStep();
			if (germ.isQualifiedToSplit()){
				Germ newGerm = germ.splitAndCopy();
				if (newGerm!=null)
					newGerms.add(newGerm);
			}
			else
				if (germ.isDeadGerm())
					deadGerms.add(germ);					
		}
		//remove all dead germs from the population
		for (Germ germ : deadGerms) {
			germs.remove(germ);
			--popSize;
		}
		//add all new germs to the population
		for (Germ germ : newGerms) {
			//maximum 80% of the matrix is allowed to be full
			if (popSize+1<maxPopSize){
				germs.add(germ);
				++popSize;
			}
		}
		//after the updated population is generated get their data for the graph
		updateGraphData();
	}
	
	/* Randomly inserts food at a random percentage of the matrix space (up to 50% maximum),
	 * except for the fluent food region which is initialized to be always filled with food
	 */
	private static void fillFood(){
		Random rand=new Random(System.currentTimeMillis());
		//random amount of NEW food each round - at least 50% of the fluent food area
		int newFoodCount=(int) ((rand.nextInt(6)+0.5)/10*M*M);
		do{
			int countFull=0;
			for (int i=locMUp[0];newFoodCount>0 && countFull<M*M && i<=locMDown[0];++i)
				for (int j=locMUp[1];newFoodCount>0 && countFull<M*M && j<=locMDown[1];++j)
					if (rand.nextGaussian()>0.5 && foodMatrix[i][j]!=1){
						foodMatrix[i][j]=1;
						--newFoodCount;
					}
					else
						if (foodMatrix[i][j]==1)
							++countFull;
			if (countFull==M*M)
				newFoodCount=0;
		}while(newFoodCount>0);
		//random amount of NEW food each round - up to 50% of the normal matrix area
		newFoodCount=(int) (rand.nextInt(6)/10*N*N);
		do{
			int x = rand.nextInt(N);
			int y = rand.nextInt(N);
			if (foodMatrix[x][y]!=1){
				foodMatrix[x][y]=1;
				--newFoodCount;
			}
		}while(newFoodCount>0);
	}
	
	
	public static void main(String[] args) {
		germs = new LinkedList<Germ>();
		
		//matrix size is 100
		N=100;
		foodMatrix = new byte[N][N];
		germsMatrix = new byte[N][N];
		
		//10 percent of the matrix is the fluent food area
		M=N/10;
		Random rand=new Random(System.currentTimeMillis());
		int MX = rand.nextInt(N-M);
		int MY = rand.nextInt(N-M);
		locMUp = new int[2];
		locMUp[0]=MX;
		locMUp[1]=MY;
		locMDown = new int[2];
		locMDown[0]=MX+M;
		locMDown[1]=MY+M;
		
		//initial energy amount is 1, and maximum amount is 2
		E=1;
		maxEnergy=2*E;
		
		/* After moving through half the Manhattan distance of the matrix you have enough "experience",
		 * and after gaining 50 percent more of your energy you have enough "strength" and then you can split
		 */
		splitAge=N/2;
		minSplitE=(float) (1.5*E);
		
		steps=N*2;	//we run twice the Manhattan distance of the matrix steps to allow sufficient exploration of the matrix
		popSize=(int) (N*N*0.1);	//10 percent of the grid is initially randomly filled with germs
		maxPopSize=(int) (0.8*N*N);	//maximum 80% of the grid can be full to leave free moving space
		
		graphData = new float[6][steps];	//2 areas * (energy, adventure, population) for each step
		
		//initialize the population
		for(int i=0;i<popSize;++i){
			Germ germ = new Germ(E);
			germs.add(germ);
		}
		
		//perform the steps
		while (steps>0){
			performStep();
			--steps;
		}
		
		//print results
		try {
			printGraphData();
		} catch (IOException e) {
			System.out.println("Error writing graph data file");
		}

	}

}
